package kernel;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;

/**
 * A set of fibers
 *
 */
public class FiberSetLite {

	public static final String LINES = "LINES";
	public static final String POINTS = "POINTS";
	public static final String START_POINTS = "START_POINTS";
	public static final String END_POINTS = "END_POINTS";

	public static final int LINE = 0;
	public static final int POINT = 1;
	public static final int START_POINT = 2;
	public static final int END_POINT = 3;

	public static final int FILEPATH = 4;
	public static final int COLOR = 5;
	public static final int RENDER_COUNT = 6;
	public static final int RENDER_TYPE = 7;

	private ArrayList<Fiber> fibers;
	private String name;
	private String filePath;
	private double[] boundingMin;
	private double[] boundingMax;
	private double fibersetBoundngBoxWidth;
	private double fibersetBoundingBoxHeight;
	private double fibersetBoundingBoxDepth;
	private double[] center;

	private boolean drawLines = true;
	private boolean drawPoints = false;
	private boolean drawStartPoints = false;
	private boolean drawEndPoints = false;
	private int fiberRenderCount = 0;
	
	private boolean withLog;

	/**
	 * Basic constructor
	 */
	public FiberSetLite()
	{
		withLog = false;
		fibers = new ArrayList<Fiber>();
		boundingMin = new double[]{Double.MAX_VALUE, Double.MAX_VALUE,Double.MAX_VALUE};
		boundingMax = new double[]{Double.MIN_VALUE, Double.MIN_VALUE,Double.MIN_VALUE};
		center = new double[3];
	}


	/**
	 * Sets the fiber render type
	 * @param renderType is a boolean[]{LINES, POINTS, START_POINTS, END_POINTS}
	 */
	public void setFiberRenderType(boolean[] renderType)
	{
		drawLines = renderType[LINE];
		drawPoints = renderType[POINT];
		drawStartPoints = renderType[START_POINT];
		drawEndPoints = renderType[END_POINT];
	}
	/**
	 * Sets the fiber render type in a String
	 * @param renderType a String with the rendering type. It must contain at least LINE, POINT, START_POINTS, or END_POINTS
	 */
	public void setFiberRenderType(String renderType)
	{
		String[] renderTypes = renderType.split(" ");
		for(String rType : renderTypes)
		{
			rType = getRendererType(rType);
			if(rType.equals(LINES))
			{
				drawLines = true;
			}
			else if(rType.equals(POINTS))
			{
				drawPoints = true;
			}
			else if(rType.equals(START_POINTS))
			{
				drawStartPoints = true;
			}
			else if(rType.equals(END_POINTS))
			{
				drawEndPoints = true;
			}
		}

	}

	/**
	 * Sets the number of fibers to render
	 * @param fiberRenderCount number of fibers to render
	 */
	public void setFiberRenderCount(int fiberRenderCount)
	{
		this.fiberRenderCount = fiberRenderCount;
	}

	/**
	 * Creates a fiber set with the given fibers
	 * @param fibers fibers that belong to this set
	 */
	public FiberSetLite(ArrayList<Fiber> fibers)
	{
		this();
		this.fibers = fibers;
	}

	/**
	 * Set the option to draw the fibers as lines
	 * @param option 
	 */
	public void drawLines(boolean option)
	{
		drawLines = option;
	}

	/**
	 * Set the option to draw the fibers as points
	 * @param option
	 */
	public void drawPoints(boolean option)
	{
		drawPoints = option;
	}

	/**
	 * Sets the option to draw the fibers start points
	 * @param option
	 */
	public void drawStartPoints(boolean option)
	{
		drawStartPoints = option;
	}

	/**
	 * Set the option to draw the fibers end points
	 * @param option
	 */
	public void drawEndPoints(boolean option)
	{
		drawEndPoints = option;
	}

	/**
	 * Set the fibers set
	 * @param fibers
	 */
	public void setFibers(ArrayList<Fiber> fibers)
	{
		this.fibers = fibers;
	}

	/**
	 * Returns the filepath
	 * @return the filePath
	 */
	public String getFilePath() {
		return filePath;
	}

	/**
	 * Returns the name of the set
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Get the rendering setup for the fiber set. Which kind of visualization will use
	 * @return lines, points, start points, end points
	 */
	public boolean[] getRenderSetup()
	{
		return new boolean[]{drawLines, drawPoints, drawStartPoints, drawEndPoints};
	}

	/**
	 * Return the fiber set
	 * @return the fiber set
	 */
	public ArrayList<Fiber> getFibers() {
		return fibers;
	}

	/**
	 * Size of the fiber set
	 * @return
	 */
	public int size() {
		return fibers.size();
	}

	/**
	 * Returns the number of fibers to render
	 * @return
	 */
	public int getFiberRenderCount() {
		return fiberRenderCount;
	}

	/**
	 * Evaluates the raw type and returns a valid rendering type
	 * @param rawType rendering type
	 * @return valid rendering type
	 */
	public static String getRendererType(String rawType)
	{
		if(!rawType.equalsIgnoreCase(LINES) && 
				!rawType.equalsIgnoreCase(POINTS) &&
				!rawType.equalsIgnoreCase(START_POINTS) &&
				!rawType.equalsIgnoreCase(END_POINTS))
		{
			return LINES;
		}
		return rawType.toUpperCase();
	}

	/**
	 * Reads a file that contains the fiber set in vtk format
	 * @param file vtk file that contains the fiber set
	 * @param color rendering color for the fiber set
	 * @throws Exception if the file could not be found
	 */
	public void readFile(File file, Color color) throws Exception
	{
		name = file.getAbsolutePath();
		System.out.println("Start Loading: " + file.getAbsolutePath());
		BufferedReader buffReader = new BufferedReader(new FileReader(file));
		String line = "";

		boolean readingpoints = false;
		boolean readinglines = false;
		ArrayList<Double> vertices = new ArrayList<Double>();
		ArrayList<double[]> points = new ArrayList<double[]>();
		int lineNumber = 1;
		while ((line =buffReader.readLine())!= null)
		{
			if(line.contains("POINTS"))
			{
//				System.out.println("File info -> " + line);
				readingpoints = true;
				continue;
			}
			if(line.contains("LINES"))
			{
//				System.out.println("File info -> " + line);
				readingpoints = false;
				readinglines = true;

				if(withLog)
					System.out.println("Vertices: " + vertices.size());
				for(int i = 0; i < vertices.size(); i+=3)
				{
					try {
						double x = vertices.get(i);
						double y = vertices.get(i + 1);
						double z = vertices.get(i + 2);

						if(x < boundingMin[0])
						{
							boundingMin[0] = x;
						}
						if(x > boundingMax[0])
						{
							boundingMax[0] = x;
						}
						if(y < boundingMin[1])
						{
							boundingMin[1] = y;
						}
						if(y > boundingMax[1])
						{
							boundingMax[1] = y;
						}
						if(z < boundingMin[2])
						{
							boundingMin[2] = z;
						}
						if(z > boundingMax[2])
						{
							boundingMax[2] = z;
						}

						points.add(new double[]{
								x,
								y,
								z
						});



					} catch (Exception e) {
						System.err.println("Vertices " + i + " could not be read");
					}
				}
				continue;
			}
			if(line.contains("POINT_DATA"))
			{

				break;
			}

			if(readingpoints)
			{
				String[] parser = line.split("\\s");
				if(parser.length % 3 != 0)
				{
					System.err.println("Error in parsing: " + lineNumber + " --- " + parser.length);
				}
				for(int i = 0; i < parser.length; i++)
				{
					try {
						vertices.add(Double.parseDouble(parser[i]));
					} catch (Exception e) {
						System.err.println("Error line " + lineNumber + " parser size: " + parser.length);
					}
				}
			}

			if(readinglines)
			{ 
				String[] parser = line.split(" ");
				ArrayList<double[]> line_i = new ArrayList<double[]>();
				for(int i = 1; i < parser.length && parser.length != 1; i++)
				{
					int index = Integer.parseInt(parser[i]);
					try {
						line_i.add(points.get(index));
					} catch (Exception e) {
						continue;
					}
				}
				if(line_i.size() != 0)
				{
					Fiber newFiber = new Fiber(line_i, color);
					newFiber.setFiberName("Fiber_"+fibers.size());
					newFiber.setFiberId(fibers.size());
					fibers.add(newFiber);
				}
			}
			lineNumber++;
		}

		buffReader.close();
		if(withLog)
		{
			System.out.println("Points: " + points.size());
			System.out.println("Lines:  " + size());
			System.out.println("Bounds:\n\tXmin,Xmax: (" +boundingMin[0] + ", " + boundingMax[0] + ")");
			System.out.println("\tYmin,Ymax: (" + boundingMin[1] + ", " + boundingMax[1] + ")");
			System.out.println("\tZmin,Zmax: (" + boundingMin[2] + ", " + boundingMax[2] + ")");
		}


		fibersetBoundngBoxWidth = (boundingMax[0] - boundingMin[0]);
		fibersetBoundingBoxHeight = (boundingMax[1] - boundingMin[1]);
		fibersetBoundingBoxDepth = (boundingMax[2] - boundingMin[2]);

		center[0] =  boundingMin[0] + fibersetBoundngBoxWidth/2;
		center[1] =  boundingMin[1] + fibersetBoundingBoxHeight/2;
		center[2] =  boundingMin[2] + fibersetBoundingBoxDepth/2;

		if(withLog)
			System.out.println("Width = " + fibersetBoundngBoxWidth + " Height: " + fibersetBoundingBoxHeight + " Depth = " + fibersetBoundingBoxDepth + " Center: " + center[0] + ", " + center[1] + ", " + center[2]);

		System.out.println("End Loading  : " + file.getAbsolutePath());
	}

	/**
	 * Set the new bounding box for the fibers and calculation of grid
	 * @param minX minimum value in X
	 * @param minY minimum value in Y
	 * @param minZ minimum value in Z
	 * @param maxX maximum value in X
	 * @param maxY maximum value in Y
	 * @param maxZ maximum value in Z
	 */
	public void setBounds(double minX, double minY, double minZ, double maxX, double maxY, double maxZ)
	{
		if(minX <= boundingMin[0] && minY <= boundingMin[1] && minZ <= boundingMin[2] &&
				maxX >= boundingMax[0] && maxY >= boundingMax[1] && maxZ >= boundingMax[2])
		{
			boundingMin[0] = minX;
			boundingMin[1] = minY;
			boundingMin[2] = minZ;
			boundingMax[0] = maxX;
			boundingMax[1] = maxY;
			boundingMax[2] = maxZ;
			System.out.println("Bounds updated");
		}
		else
		{
			System.out.println("New bounds does not contain old ones");
		}
		System.out.println("Bounds:\n\tXmin,Xmax: (" +boundingMin[0] + ", " + boundingMax[0] + ")");
		System.out.println("\tYmin,Ymax: (" + boundingMin[1] + ", " + boundingMax[1] + ")");
		System.out.println("\tZmin,Zmax: (" + boundingMin[2] + ", " + boundingMax[2] + ")");
	}
	/**
	 * Writes a file with only the points of the fiber set
	 * @param file file to be written
	 * @throws Exception if the file does not exist
	 */
	public void writeFile(File file) throws Exception
	{
		System.out.println("Writing file " + file.getAbsolutePath());
		FileWriter out = new FileWriter(file);
		for(Fiber f : fibers)
		{
			for(double[] p : f.getPoints())
			{
				out.write(p[0] + " " + p[1] + " " + p[2] + "\n");
			}
		}
		out.close();
		System.out.println("End writing file " + file.getAbsolutePath());
	}


	/**
	 * Returns the minimum point of the bounding box for the fiber set
	 * @return minimum point
	 */
	public double[] getBoundingMin() {
		return boundingMin;
	}

	/**
	 * Returns the maximum point of the bounding box for the fiber set
	 * @return maximum point
	 */
	public double[] getBoundingMax() {
		return boundingMax;
	}

	/**
	 * Returns the width of the bounding box of the fiber set
	 * @return width
	 */
	public double getWidth() {
		return fibersetBoundngBoxWidth;
	}

	/**
	 * Returns the height of the bounding box of the fiber set
	 * @return height
	 */
	public double getHeight() {
		return fibersetBoundingBoxHeight;
	}

	/**
	 * Returns the depth of the bounding box of the fiber set
	 * @return depth
	 */
	public double getDepth() {
		return fibersetBoundingBoxDepth;
	}

	/**
	 * Returns the center of the bounding box of the fiber set
	 * @return center
	 */
	public double[] getCenter()
	{
		return center;
	}

	
	public void setLoggingOn()
	{
		withLog = true;
	}

	public static void main(String[] args)
	{
		int width = 3;
		int height = 4;
		int depth = 5;
		for(int i = 0; i < width; i++)
		{
			for(int j = 0; j < height; j++)
			{
				for(int k = 0; k < depth; k++)
				{
					int position = (height * depth * i) + (depth * j) + k;
					System.out.println(i + ", " + j + ", " + k + ": " + position);
				}	
			}	
		}
	}

	
}
